import './jquery.expr';

const SUPPORTED_EVENTS = [
    'auxclick',
    'click',
    'dblclick',
    'mousedown',
    'mouseup',
    'mouseover',
    'mousemove',
    'mouseout',
    'mouseenter',
    'mouseleave',
    'mousewheel',
    'wheel',
    'contextmenu',
    'keydown',
    'keyup',
    'keypress',
    'input',
    'drag',
    'dragstart',
    'dragend',
    'dragenter',
    'dragenter',
    'dragover',
    'dragleave',
    'drop',
    'load',
    'beforeunload',
    'unload',
    'abort',
    'error'
    // 'play',
    // 'pause',
    // 'playing',
    // 'canplay',
    // 'canplaythrough',
    // 'seeking',
    // 'seeked',
    // 'timeupdate',
    // 'ended',
    // 'ratechange',
    // 'durationchange',
    // 'volumechange',
    // 'loadstart',
    // 'progress',
    // 'suspend',
    // 'emptied',
    // 'stalled',
    // 'loadedmetadata',
    // 'loadeddata',
    // 'waiting',
    // 'pointerover',
    // 'pointerout',
    // 'pointerenter',
    // 'pointerleave',
    // 'pointerdown',
    // 'pointerup',
    // 'pointermove',
    // 'pointercancel',
    // 'gotpointercapture',
    // 'lostpointercapture'
];
const GLOBAL_ACTIONS = {
    root: {
        stopPropagation: e => e.stopPropagation(),
        preventDefault: e => e.preventDefault()
    }
};
const IS_BIND_FLAG = random('action_bind_');
const BIND_EVENT_ALIAS = random('action_bind_alias_');
const BIND_ACTION_FLAG = random('action_bind_action_');

function random(pref) {
    return (
        pref +
        Math.random()
            .toString(16)
            .substring(2)
    );
}

$.actions = function(name, actions) {
    if (arguments.length === 1) {
        return GLOBAL_ACTIONS[name];
    } else {
        GLOBAL_ACTIONS[name] = actions;
    }
};

function bindActions(target, actions) {
    var $target = $(target);
    const isBind = $target.data(IS_BIND_FLAG);
    if (isBind) {
        const oldAction = $target.data(BIND_ACTION_FLAG);
        const alias = $target.data(BIND_EVENT_ALIAS);
        $target.removeData(BIND_EVENT_ALIAS);
        $target.removeData(BIND_ACTION_FLAG);
        $target.off(alias);
    }
    const alias = random('.');
    $target.data(BIND_EVENT_ALIAS, alias);
    $target.data(BIND_ACTION_FLAG, actions);
    $target.data(IS_BIND_FLAG, true);
    SUPPORTED_EVENTS.forEach(eventName => {
        const childSelector = `[\\@${eventName}],[bind-${eventName}]`;
        const $children = $target.find(childSelector);
        if ($children.length === 0) {
            return;
        }
        $target.on(`${eventName}${alias}`, childSelector, function (e) {
            const $this = $(this);
            const actionName = $this.attr(`@${eventName}`) || $this.attr(`bind-${eventName}`);
            const handle = actions[actionName];
            if(typeof handle === 'function') {
                return handle.call($this, e, actions);
            }
        });
    });
}

$(function() {
    var observer = new MutationObserver(function(mutationsList) {
        for (var mutation of mutationsList) {
            switch (mutation.type) {
                case 'childList':
                    let nodes = [];
                    mutation.addedNodes.forEach(node => {
                        if(node.nodeName !== '#text') {
                            nodes.push(node);
                        }
                    });
                    if (nodes.length > 0) {
                        attachActions(nodes);
                    }
                    break;
                case 'attributes':
                    attachActions([mutation.target]);
                    console.info(mutation);
                    break;
            }
        }
    });
    observer.observe(document.body, {
        childList: true,
        attributes: true,
        attributeFilter: SUPPORTED_EVENTS.map(name => 'bind-' + name),
        subtree: true
    });

    attachActions($('[\\@actions],[bind-actions]').toArray());

    function attachActions(nodes) {
        nodes.forEach(function(node) {
            const $node = $(node);
            const actionName = $node.attr('@actions') || $node.attr('bind-actions');
            let actions;
            if (actionName) {
                actions = $.actions(actionName);
                if (!actions) {
                    throw new Error(`Action not defined: ${actionName}`);
                }
            }
            let parentAction = $.actions('root');
            const $closestActionElm = $node.parent().closest('[\\@actions]');
            if ($closestActionElm.length > 0) {
                const closestActionName = $closestActionElm.attr('@actions');
                parentAction = $.actions(closestActionName);
            }
            if (!actions) {
                actions = parentAction || {};
            }
            if (parentAction && parentAction !== actions) {
                Object.setPrototypeOf(actions, parentAction);
            }
            bindActions($closestActionElm, actions);
        });
    }
});
